import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;

/**
 * 
 * Authors - Oren Bracha, Rony Lahav and Yehuda Eterman
 * 
 * handles a single update session for a single peer
 */
public class SingleUpdate implements Runnable{
	
	DBTuple tupleForUpdate;
	long uniqueID;
	
	public SingleUpdate(DBTuple tupleforUpdate, long uniqueID) {
		this.uniqueID = uniqueID;
		this.tupleForUpdate = tupleforUpdate;
	}
	
	public void run(){
		try{
			//try 3 times with UDP
			if (!UDPAttempt(tupleForUpdate) && proj350.getSleepPeriod() == 0)
			{
				//try update with TCP
				if (!TCPAttempt(tupleForUpdate)) {
					// if unsuccessful set peer as offline
					DataBase.getInstance().setPeerOffline(tupleForUpdate.getPeerName());
					Logger.getInstance().printErrorNotAvailable(tupleForUpdate.getPeerName());
				}
			}
		}
		catch(Exception e){
			
		}
		finally{
			proj350.removeThreadFromLivingList(uniqueID);
		}
	}
	
	/**
	 * 
	 * This method try to send a periodical update message to a specific peer in UDP protocol. It tries
	 * to contact the peer 3 times before giving up
	 */
	public boolean UDPAttempt(DBTuple tuple) {
		boolean UDPConnSucc = false;
		int counter = 0; // Counter for how many update messages were actually sent with no erro
		
		while (counter < proj350.UDP_ATTEMPTS_NUMBER && !UDPConnSucc & proj350.getSleepPeriod() == 0){
			 DatagramSocket ds = null;
			try {	
				//create socket and try to send an update
				ds = new DatagramSocket();
				String message = createUpdateMessage();
				DatagramPacket dp = new DatagramPacket(message.getBytes(), message.length(), InetAddress.getByName(tuple.getIp()),Integer.parseInt(tuple.getUdpPort()));
				ds.send(dp); // Here we send the update
				Logger.getInstance().printInOutMsg(false, message, proj350.getMyPeerName(), proj350.getMyIp(), Integer.toString(ds.getLocalPort()), tuple.getPeerName(), tuple.getIp(), tuple.getUdpPort(), "udp");
				++counter;

				//set timeout to wait for answer (request message)
				ds.setSoTimeout(proj350.TIMEOUT_IDLE_PERIOD);

				byte[] buffer = new byte[1024];
				DatagramPacket rcvPacket = new DatagramPacket(buffer, buffer.length);
				ds.receive(rcvPacket); // Wait for answer from the other side
				
				// Answer was received, open a dedicated thread for the rest of the session
				UDPConnSucc = true;
				UDPSenderConnection udpConnSender = new UDPSenderConnection(tuple.getPeerName(),tuple.getIp(),ds,rcvPacket.getPort(),rcvPacket,proj350.getNewUniqueID());
				Thread thread  = new Thread(udpConnSender);
				proj350.addThreadToLivingList(udpConnSender.getUniqueID(), thread);
				thread.start();
				}
			catch (SocketTimeoutException e){
				if (ds != null && !ds.isClosed()){
					ds.close();
				}
			}
			catch (IOException e){
			}
		}
		return UDPConnSucc;
	}

	/**
	 * 
	 * This method try to send a periodical update message to a specific peer in TCP protocol. 
	 */
	public boolean TCPAttempt(DBTuple tuple) {
		try{
			//create socket and send update message
			Socket socket = new Socket(tuple.getIp(),Integer.parseInt(tuple.getTcpPort()));
			TCPSenderConnection tcpSenderConn = new TCPSenderConnection(socket, tuple.getPeerName(), tuple.getIp(),proj350.getNewUniqueID());
			Thread thread = new Thread(tcpSenderConn);
			proj350.addThreadToLivingList(tcpSenderConn.getUniqueID(), thread);
			thread.start();
			return true;
		}
		
		catch (IOException e){
			return false;
		}
	}

	/**
	 * create update message in correct format
	 * @return update message
	 */
	public String createUpdateMessage() {
		String message = MessageCenter.getInstance().createMessage_Update(proj350.getMyTuple().getDbVersion(), proj350.getMyPeerName());
		return message;
	}

	public long getUniqueID(){
		return uniqueID;
	}
}
